home *** CD-ROM | disk | FTP | other *** search
/ Experimental BBS Explossion 3 / Experimental BBS Explossion III.iso / disk / xdu21dvx.zip / XDU.C < prev    next >
C/C++ Source or Header  |  1993-07-27  |  16KB  |  770 lines

  1. /*
  2.  *            X D U . C
  3.  *
  4.  * Display the output of "du" in an X window.
  5.  *
  6.  * Phillip C. Dykstra
  7.  * <phil@arl.army.mil>
  8.  * 4 Sep 1991.
  9.  * 
  10.  * Copyright (C)    Phillip C. Dykstra    1991, 1993
  11.  * Permission to use, copy, modify, distribute, and sell this software
  12.  * and its documentation for any purpose is hereby granted without fee,
  13.  * provided that the above copyright notice appear in all copies and that
  14.  * both that copyright notice and this permission notice appear in
  15.  * supporting documentation, and that the authors name not be used in
  16.  * advertising or publicity pertaining to distribution of the software
  17.  * without specific, written prior permission.  The author makes no
  18.  * representations about the suitability of this software for any purpose.
  19.  * It is provided "as is" without express or implied warranty.
  20.  */
  21. #include <stdio.h>
  22. #include "version.h"
  23.  
  24. extern char *malloc(), *calloc();
  25.  
  26. #define    MAXDEPTH    80    /* max elements in a path */
  27. #define    MAXNAME        1024    /* max pathname element length */
  28. #define    MAXPATH        4096    /* max total pathname length */
  29. #define    NCOLS        5    /* default number of columns in display */
  30.  
  31. /* What we IMPORT from xwin.c */
  32. extern int xsetup(), xmainloop(), xdrawrect(), xrepaint();
  33.  
  34. /* What we EXPORT to xwin.c */
  35. extern int press(), reset(), repaint(), setorder(), reorder();
  36. extern nodeinfo(), helpinfo();
  37. int ncols = NCOLS;
  38.  
  39. /* internal routines */
  40. char *strdup();
  41. void addtree();
  42. void parse_file();
  43. void parse_entry();
  44. void dumptree();
  45. void clearrects();
  46. void sorttree();
  47.  
  48. /* order to sort paths by */
  49. #define    ORD_FIRST    1
  50. #define    ORD_LAST    2
  51. #define    ORD_ALPHA    3
  52. #define    ORD_SIZE    4
  53. #define    ORD_RALPHA    5
  54. #define    ORD_RSIZE    6
  55. #define    ORD_DEFAULT    ORD_FIRST
  56. int order = ORD_DEFAULT;
  57.  
  58. /*
  59.  * Rectangle Structure
  60.  * Stores window coordinates of a displayed rectangle
  61.  * so that we can "find" it again on key presses.
  62.  */
  63. struct rect {
  64.     int    left;
  65.     int    top;
  66.     int    width;
  67.     int    height;
  68. };
  69.  
  70. /*
  71.  * Node Structure
  72.  * Each node in the path tree is linked in with one of these.
  73.  */
  74. struct node {
  75.     char    *name;
  76.     long    size;        /* from here down in the tree */
  77.     long    num;        /* entry number - for resorting */
  78.     struct    rect rect;    /* last drawn screen rectangle */
  79.     struct    node *peer;    /* siblings */
  80.     struct    node *child;    /* list of children if !NULL */
  81.     struct    node *parent;    /* backpointer to parent */
  82. } top;
  83. struct node *topp = ⊤
  84. #define    NODE_NULL ((struct node *)0)
  85. long nnodes = 0;
  86.  
  87. /*
  88.  * create a new node with the given name and size info
  89.  */
  90. struct node *
  91. makenode(name,size)
  92. char *name;
  93. int size;
  94. {
  95.     struct    node    *np;
  96.  
  97.     np = (struct node *)calloc(1,sizeof(struct node));
  98.     np->name = strdup(name);
  99.     np->size = size;
  100.     np->num = nnodes;
  101.     nnodes++;
  102.  
  103.     return    np;
  104. }
  105.  
  106. /*
  107.  * Return the node (if any) which has a draw rectangle containing
  108.  * the given x,y point.
  109.  */
  110. struct node *
  111. findnode(treep, x, y)
  112. struct    node *treep;
  113. int    x, y;
  114. {
  115.     struct    node    *np;
  116.     struct    node    *np2;
  117.  
  118.     if (treep == NODE_NULL)
  119.         return    NODE_NULL;
  120.  
  121.     if (x >= treep->rect.left && x < treep->rect.left+treep->rect.width
  122.      && y >= treep->rect.top && y < treep->rect.top+treep->rect.height) {
  123.         /*printf("found %s\n", treep->name);*/
  124.         return    treep;    /* found */
  125.     }
  126.  
  127.     /* for each child */
  128.     for (np = treep->child; np != NULL; np = np->peer) {
  129.         if ((np2 = findnode(np,x,y)) != NODE_NULL)
  130.             return    np2;
  131.     }
  132.     return    NODE_NULL;
  133. }
  134.  
  135. /*
  136.  * return a count of the number of children of a given node
  137.  */
  138. int
  139. numchildren(nodep)
  140. struct node *nodep;
  141. {
  142.     int    n;
  143.  
  144.     if (nodep == NODE_NULL)
  145.         return    0;
  146.  
  147.     n = 0;
  148.     for (nodep = nodep->child; nodep != NODE_NULL; nodep=nodep->peer)
  149.         n++;
  150.  
  151.     return    n;
  152. }
  153.  
  154. /*
  155.  * fix_tree - This function repairs the tree when certain nodes haven't
  156.  *           had their sizes initialized. [DPT911113]
  157.  *          * * * This function is recursive * * *
  158.  */
  159. long
  160. fix_tree(top)
  161. struct node *top;
  162. {
  163.     struct node *nd;
  164.  
  165.     if (top == NODE_NULL)        /* recursion end conditions */
  166.         return 0;
  167.     if (top->size >= 0)         /* also halt recursion on valid size */
  168.         return top->size;    /* (remember: sizes init. to -1) */
  169.  
  170.     top->size = 0;
  171.     for (nd = top->child; nd != NODE_NULL; nd = nd->peer)
  172.         top->size += fix_tree(nd);
  173.  
  174.     return top->size;
  175. }
  176.  
  177. static char usage[] = "\
  178. Usage: xdu [-options ...] filename\n\
  179.    or  xdu [-options ...] < du.out\n\
  180. \n\
  181. Graphically displays the output of du in an X window\n\
  182.   options include:\n\
  183.   -s          Don't display size information\n\
  184.   +s          Display size information (default)\n\
  185.   -n          Sort in numerical order (largest first)\n\
  186.   -rn         Sort in reverse numerical order\n\
  187.   -a          Sort in alphabetical order\n\
  188.   -ra         Sort in reverse alphabetical order\n\
  189.   -c num      Set number of columns to num\n\
  190.   Toolkit options: -fg, -bg, -rv, -display, -geometry, etc.\n\
  191. ";
  192.  
  193. main(argc,argv)
  194. int argc;
  195. char **argv;
  196. {
  197.     top.name = strdup("[root]");
  198.     top.size = -1;
  199.  
  200.     xsetup(&argc,argv);
  201.     if (argc == 1) {
  202.         if (isatty(fileno(stdin))) {
  203.             fprintf(stderr, usage);
  204.             exit(1);
  205.         } else {
  206.             parse_file("-");
  207.         }
  208.     } else if (argc == 2 && strcmp(argv[1],"-help") != 0) {
  209.         parse_file(argv[1]);
  210.     } else {
  211.         fprintf(stderr, usage);
  212.         exit(1);
  213.     }
  214.     top.size = fix_tree(&top);
  215.  
  216.     /*dumptree(&top,0);*/
  217.     if (order != ORD_DEFAULT)
  218.         sorttree(&top, order);
  219.  
  220.     topp = ⊤
  221.     /* don't display root if only one child */
  222.     if (numchildren(topp) == 1)
  223.         topp = topp->child;
  224.  
  225.     xmainloop();
  226.     exit(0);
  227. }
  228.  
  229. void
  230. parse_file(filename)
  231. char *filename;
  232. {
  233.     char    buf[4096];
  234.     char    name[4096];
  235.     int    size;
  236.     FILE    *fp;
  237.  
  238.     if (strcmp(filename, "-") == 0) {
  239.         fp = stdin;
  240.     } else {
  241.         if ((fp = fopen(filename, "r")) == 0) {
  242.             fprintf(stderr, "xdu: can't open \"%s\"\n", filename);
  243.             exit(1);
  244.         }
  245.     }
  246.     while (fgets(buf,sizeof(buf),fp) != NULL) {
  247.         sscanf(buf, "%d %s\n", &size, name);
  248.         /*printf("%d %s\n", size, name);*/
  249.         parse_entry(name,size);
  250.     }
  251.     fclose(fp);
  252. }
  253.  
  254. /* bust up a path string and link it into the tree */
  255. void
  256. parse_entry(name,size)
  257. char *name;
  258. int size;
  259. {
  260.     char    *path[MAXDEPTH]; /* break up path into this list */
  261.     char    buf[MAXNAME];     /* temp space for path element name */
  262.     int    arg, indx;
  263.     int    length;        /* nelson@reed.edu - trailing / fix */
  264.  
  265.     if (*name == '/')
  266.         name++;        /* skip leading / */
  267.  
  268.     length = strlen(name);
  269.     if ((length > 0) && (name[length-1] == '/')) {
  270.         /* strip off trailing / (e.g. GNU du) */
  271.         name[length-1] = 0;
  272.     }
  273.  
  274.     arg = 0; indx = 0;
  275.     bzero(path,sizeof(path));
  276.     bzero(buf,sizeof(buf));
  277.     while (*name != NULL) {
  278.         if (*name == '/') {
  279.             buf[indx] = 0;
  280.             path[arg++] = strdup(buf);
  281.             indx = 0;
  282.             if (arg >= MAXDEPTH)
  283.                 break;
  284.         } else {
  285.             buf[indx++] = *name;
  286.             if (indx >= MAXNAME)
  287.                 break;
  288.         }
  289.         name++;
  290.     }
  291.     buf[indx] = 0;
  292.     path[arg++] = strdup(buf);
  293.     path[arg] = NULL;
  294.  
  295.     addtree(&top,path,size);
  296. }
  297.  
  298. /*
  299.  *  Determine where n1 should go compared to n2
  300.  *    based on the current sorting order.
  301.  *  Return -1 if is should be before.
  302.  *          0 if it is a toss up.
  303.  *        1 if it should go after.
  304.  */
  305. int
  306. compare(n1,n2,order)
  307. struct node *n1, *n2;
  308. int order;
  309. {
  310.     int    ret;
  311.  
  312.     switch (order) {
  313.     case ORD_SIZE:
  314.         ret = n2->size - n1->size;
  315.         if (ret == 0)
  316.             return strcmp(n1->name,n2->name);
  317.         else
  318.             return ret;
  319.         break;
  320.     case ORD_RSIZE:
  321.         ret = n1->size - n2->size;
  322.         if (ret == 0)
  323.             return strcmp(n1->name,n2->name);
  324.         else
  325.             return ret;
  326.         break;
  327.     case ORD_ALPHA:
  328.         return strcmp(n1->name,n2->name);
  329.         break;
  330.     case ORD_RALPHA:
  331.         return strcmp(n2->name,n1->name);
  332.         break;
  333.     case ORD_FIRST:
  334.         /*return -1;*/
  335.         return (n1->num - n2->num);
  336.         break;
  337.     case ORD_LAST:
  338.         /*return 1;*/
  339.         return (n2->num - n1->num);
  340.         break;
  341.     }
  342.  
  343.     /* shouldn't get here */
  344.     fprintf(stderr,"xdu: bad insertion order\n");
  345.     return    0;
  346. }
  347.  
  348. void
  349. insertchild(nodep,childp,order)
  350. struct node *nodep;    /* parent */
  351. struct node *childp;    /* child to be added */
  352. int order;        /* FIRST, LAST, ALPHA, SIZE */
  353. {
  354.     struct node *np, *np1;
  355.  
  356.     if (nodep == NODE_NULL || childp == NODE_NULL)
  357.         return;
  358.     if (childp->peer != NODE_NULL) {
  359.         fprintf(stderr, "xdu: can't insert child with peers\n");
  360.         return;
  361.     }
  362.  
  363.     childp->parent = nodep;
  364.     if (nodep->child == NODE_NULL) {
  365.         /* no children, order doesn't matter */
  366.         nodep->child = childp;
  367.         return;
  368.     }
  369.     /* nodep has at least one child already */
  370.     if (compare(childp,nodep->child,order) < 0) {
  371.         /* new first child */
  372.         childp->peer = nodep->child;
  373.         nodep->child = childp;
  374.         return;
  375.     }
  376.     np1 = nodep->child;
  377.     for (np = np1->peer; np != NODE_NULL; np = np->peer) {
  378.         if (compare(childp,np,order) < 0) {
  379.             /* insert between np1 and np */
  380.             childp->peer = np;
  381.             np1->peer = childp;
  382.             return;
  383.         }
  384.         np1 = np;
  385.     }
  386.     /* at end, link new child on */
  387.     np1->peer = childp;
  388. }
  389.  
  390. /* add path as a child of top - recursively */
  391. void
  392. addtree(top, path, size)
  393. struct node *top;
  394. char *path[];
  395. int size;
  396. {
  397.     struct    node *np;
  398.  
  399.     /*printf("addtree(\"%s\",\"%s\",%d)\n", top->name, path[0], size);*/
  400.  
  401.     /* check all children for a match */
  402.     for (np = top->child; np != NULL; np = np->peer) {
  403.         if (strcmp(path[0],np->name) == 0) {
  404.             /* name matches */
  405.             if (path[1] == NULL) {
  406.                 /* end of the chain, save size */
  407.                 np->size = size;
  408.                 return;
  409.             }
  410.             /* recurse */
  411.             addtree(np,&path[1],size);
  412.             return;
  413.         }
  414.     }
  415.     /* no child matched, add a new child */
  416.     np = makenode(path[0],-1);
  417.     insertchild(top,np,order);
  418.  
  419.     if (path[1] == NULL) {
  420.         /* end of the chain, save size */
  421.         np->size = size;
  422.         return;
  423.     }
  424.     /* recurse */
  425.     addtree(np,&path[1],size);
  426.     return;
  427. }
  428.  
  429. /* debug tree print */
  430. void
  431. dumptree(np,level)
  432. struct node *np;
  433. int level;
  434. {
  435.     int    i;
  436.     struct    node *subnp;
  437.  
  438.     for (i = 0; i < level; i++)
  439.         printf("   ");
  440.  
  441.     printf("%s %d\n", np->name, np->size);
  442.     for (subnp = np->child; subnp != NULL; subnp = subnp->peer) {
  443.         dumptree(subnp,level+1);
  444.     }
  445. }
  446.  
  447. void
  448. sorttree(np, order)
  449. struct node *np;
  450. int order;
  451. {
  452.     struct    node *subnp;
  453.     struct    node *np0, *np1, *np2, *np3;
  454.  
  455.     /* sort the trees of each of this nodes children */
  456.     for (subnp = np->child; subnp != NODE_NULL; subnp = subnp->peer) {
  457.         sorttree(subnp, order);
  458.     }
  459.     /* then put the given nodes children in order */
  460.     np0 = np;    /* np0 points to node before np1 */
  461.     for (np1 = np->child; np1 != NODE_NULL; np1 = np1->peer) {
  462.         np2 = np1;    /* np2 points to node before np3 */
  463.         for (np3 = np1->peer; np3 != NODE_NULL; np3 = np3->peer) {
  464.             if (compare(np3,np1,order) < 0) {
  465.                 /* swap links */
  466.                 if (np0 == np)
  467.                     np0->child = np3;
  468.                 else
  469.                     np0->peer = np3;
  470.                 np2->peer = np3->peer;
  471.                 np3->peer = np1;
  472.  
  473.                 /* adjust pointers */
  474.                 np1 = np3;
  475.                 np3 = np2;
  476.             }
  477.             np2 = np3;
  478.         }
  479.         np0 = np1;
  480.     }
  481. }
  482.  
  483. /*
  484.  * Draws a node in the given rectangle, and all of its children
  485.  * to the "right" of the given rectangle.
  486.  */
  487. drawnode(nodep, rect)
  488. struct node *nodep;    /* node whose children we should draw */
  489. struct rect rect;    /* rectangle to draw all children in */
  490. {
  491.     struct rect subrect;
  492.  
  493.     /*printf("Drawing \"%s\" %d\n", nodep->name, nodep->size);*/
  494.  
  495.     xdrawrect(nodep->name, nodep->size,
  496.         rect.left,rect.top,rect.width,rect.height);
  497.  
  498.     /* save current screen rectangle for lookups */
  499.     nodep->rect.left = rect.left;
  500.     nodep->rect.top = rect.top;
  501.     nodep->rect.width = rect.width;
  502.     nodep->rect.height = rect.height;
  503.  
  504.     /* draw children in subrectangle */
  505.     subrect.left = rect.left+rect.width;
  506.     subrect.top = rect.top;
  507.     subrect.width = rect.width;
  508.     subrect.height = rect.height;
  509.     drawchildren(nodep, subrect);
  510. }
  511.  
  512. /*
  513.  * Draws all children of a node within the given rectangle.
  514.  * Recurses on children.
  515.  */
  516. drawchildren(nodep, rect)
  517. struct node *nodep;    /* node whose children we should draw */
  518. struct rect rect;    /* rectangle to draw all children in */
  519. {
  520.     int    totalsize;
  521.     int    totalheight;
  522.     struct    node    *np;
  523.     double    fractsize;
  524.     int    height;
  525.     int    top;
  526.  
  527.     /*printf("Drawing children of \"%s\", %d\n", nodep->name, nodep->size);*/
  528.     /*printf("In [%d,%d,%d,%d]\n", rect.left,rect.top,rect.width,rect.height);*/
  529.  
  530.     top = rect.top;
  531.     totalheight = rect.height;
  532.     totalsize = nodep->size;
  533.     if (totalsize == 0) {
  534.         /* total the sizes of the children */
  535.         totalsize = 0;
  536.         for (np = nodep->child; np != NULL; np = np->peer)
  537.             totalsize += np->size;
  538.         nodep->size = totalsize;
  539.     }
  540.  
  541.     /* for each child */
  542.     for (np = nodep->child; np != NULL; np = np->peer) {
  543.         fractsize = np->size / (double)totalsize;
  544.         height = fractsize * totalheight + 0.5;
  545.         if (height > 1) {
  546.             struct rect subrect;
  547.             /*printf("%s, drawrect[%d,%d,%d,%d]\n", np->name,
  548.                 rect.left,top,rect.width,height);*/
  549.             xdrawrect(np->name, np->size,
  550.                 rect.left,top,rect.width,height);
  551.  
  552.             /* save current screen rectangle for lookups */
  553.             np->rect.left = rect.left;
  554.             np->rect.top = top;
  555.             np->rect.width = rect.width;
  556.             np->rect.height = height;
  557.  
  558.             /* draw children in subrectangle */
  559.             subrect.left = rect.left+rect.width;
  560.             subrect.top = top;
  561.             subrect.width = rect.width;
  562.             subrect.height = height;
  563.             drawchildren(np, subrect);
  564.  
  565.             top += height;
  566.         }
  567.     }
  568. }
  569.  
  570. /*
  571.  * clear the rectangle information of a given node
  572.  * and all of its decendents
  573.  */
  574. void
  575. clearrects(nodep)
  576. struct    node *nodep;
  577. {
  578.     struct    node    *np;
  579.  
  580.     if (nodep == NODE_NULL)
  581.         return;
  582.  
  583.     nodep->rect.left = 0;
  584.     nodep->rect.top = 0;
  585.     nodep->rect.width = 0;
  586.     nodep->rect.height = 0;
  587.  
  588.     /* for each child */
  589.     for (np = nodep->child; np != NULL; np = np->peer) {
  590.         clearrects(np);
  591.     }
  592. }
  593.  
  594. pwd()
  595. {
  596.     struct node *np;
  597.     struct node *stack[MAXDEPTH];
  598.     int num = 0;
  599.     struct node *rootp;
  600.     char path[MAXPATH];
  601.  
  602.     rootp = ⊤
  603.     if (numchildren(rootp) == 1)
  604.         rootp = rootp->child;
  605.  
  606.     np = topp;
  607.     while (np != NODE_NULL) {
  608.         stack[num++] = np;
  609.         if (np == rootp)
  610.             break;
  611.         np = np->parent;
  612.     }
  613.  
  614.     path[0] = '\0';
  615.     while (--num >= 0) {
  616.         strcat(path,stack[num]->name);
  617.         if (num != 0)
  618.             strcat(path,"/");
  619.     }
  620.     printf("%s %d (%.2f%%)\n", path, topp->size,
  621.         100.0*topp->size/rootp->size);
  622. }
  623.  
  624. char *
  625. strdup(s)
  626. char *s;
  627. {
  628.     int    n;
  629.     char    *cp;
  630.  
  631.     n = strlen(s);
  632.     cp = malloc(n+1);
  633.     strcpy(cp,s);
  634.  
  635.     return    cp;
  636. }
  637.  
  638. /**************** External Entry Points ****************/
  639.  
  640. int
  641. press(x,y)
  642. int x, y;
  643. {
  644.     struct node *np;
  645.  
  646.     /*printf("press(%d,%d)...\n",x,y);*/
  647.     np = findnode(&top,x,y);
  648.     /*printf("Found \"%s\"\n", np?np->name:"(null)");*/
  649.     if (np == topp) {
  650.         /* already top, go up if possible */
  651.         if (np->parent != &top || numchildren(&top) != 1)
  652.             np = np->parent;
  653.         /*printf("Already top, parent = \"%s\"\n", np?np->name:"(null)");*/
  654.     }
  655.     if (np != NODE_NULL) {
  656.         topp = np;
  657.         xrepaint();
  658.     }
  659. }
  660.  
  661. int
  662. reset()
  663. {
  664.     topp = ⊤
  665.     if (numchildren(topp) == 1)
  666.         topp = topp->child;
  667.     xrepaint();
  668. }
  669.  
  670. int
  671. repaint(width,height)
  672. int width, height;
  673. {
  674.     struct    rect rect;
  675.  
  676.     /* define a rectangle to draw into */
  677.     rect.top = 0;
  678.     rect.left = 0;
  679.     rect.width = width/ncols;
  680.     rect.height = height;
  681.  
  682.     clearrects(&top);    /* clear current rectangle info */
  683.     drawnode(topp,rect);    /* draw tree into given rectangle */
  684. #if 0
  685.     pwd();            /* display current path */
  686. #endif
  687. }
  688.  
  689. int
  690. setorder(op)
  691. char *op;
  692. {
  693.     if (strcmp(op, "size") == 0) {
  694.         order = ORD_SIZE;
  695.     } else if (strcmp(op, "rsize") == 0) {
  696.         order = ORD_RSIZE;
  697.     } else if (strcmp(op, "alpha") == 0) {
  698.         order = ORD_ALPHA;
  699.     } else if (strcmp(op, "ralpha") == 0) {
  700.         order = ORD_RALPHA;
  701.     } else if (strcmp(op, "first") == 0) {
  702.         order = ORD_FIRST;
  703.     } else if (strcmp(op, "last") == 0) {
  704.         order = ORD_LAST;
  705.     } else if (strcmp(op, "reverse") == 0) {
  706.         switch (order) {
  707.         case ORD_ALPHA:
  708.             order = ORD_RALPHA;
  709.             break;
  710.         case ORD_RALPHA:
  711.             order = ORD_ALPHA;
  712.             break;
  713.         case ORD_SIZE:
  714.             order = ORD_RSIZE;
  715.             break;
  716.         case ORD_RSIZE:
  717.             order = ORD_SIZE;
  718.             break;
  719.         case ORD_FIRST:
  720.             order = ORD_LAST;
  721.             break;
  722.         case ORD_LAST:
  723.             order = ORD_FIRST;
  724.             break;
  725.         }
  726.     } else {
  727.         fprintf(stderr, "xdu: bad order \"%s\"\n", op);
  728.     }
  729. }
  730.  
  731. int
  732. reorder(op)
  733. char *op;    /* order name */
  734. {
  735.     setorder(op);
  736.     sorttree(topp, order);
  737.     xrepaint();
  738. }
  739.  
  740. int
  741. nodeinfo()
  742. {
  743.     struct node *np;
  744.  
  745.     /* display current root path */
  746.     pwd();
  747.  
  748.     /* display each child of this node */
  749.     for (np = topp->child; np != NULL; np = np->peer) {
  750.         printf("%-8d %s\n", np->size, np->name);
  751.     }
  752. }
  753.  
  754. int
  755. helpinfo()
  756. {
  757.     fprintf(stdout, "\n\
  758. XDU Version %s - Keyboard Commands\n\
  759.   a  sort alphabetically\n\
  760.   n  sort numerically (largest first)\n\
  761.   f  sort first-in-first-out\n\
  762.   l  sort last-in-first-out\n\
  763.   r  reverse sort\n\
  764.   /  goto the root\n\
  765.   q  quit (also Escape)\n\
  766.   i  info to standard out\n\
  767. 0-9  set number of columns (0=10)\n\
  768. ", XDU_VERSION);
  769. }
  770.